package cg.server.game;

import cg.common.GameFinishedMessage;
import cg.server.Writehighscore;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.io.IOException;
import java.util.Scanner;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Ablauf und Logik des Spiels<br> <br>
 *
 * Die Klasse Spiel beinhaltet die ganze Spiellogik mit deren Werten
 *
 * @author Christian Friedli
 * @version 1.0
 */
public class Spiel implements Serializable, Runnable {

    final static int MAXSPIELER = 4;
    private static int aktuellerSpieler = 1;
    // Methode um den aktuellen Spieler zurück zugeben

    public static int getAktuellerSpieler() {
        return aktuellerSpieler;
    }
    // Spieler Array
    private Spieler[] Spieler;
    // Reihen Array
    private Reihe Reihe[];
    // Anzahl Karten auf dem Tisch / Karten die einer Reihe zugeteilt sind
    public int anzahlKartenAufTisch = 0;
    // Position der letzten Rundenkarte (von unten)
    int letzteRundeKarte = 15;
    // Instanz der Klasse GemischterStapel
    GemischterStapel GemischterStapel;
    // Wurde die letzte Rundekarte gezogen
    boolean letzteRundekarteGezogen = false;
    // Spiel Ende
    boolean spielEnde = false;
    // Runden Ende
    boolean rundeEnde = false;
    // wie viele Reihen sind aktiv / noch nicht aufgenommen
    int anzahlAktiverReihen = 4;
    // Wie viele der aktiven Reihen sind voll?
    int anzahlAktiverVollerReihen = 0;
    // Wurde eine Karte vom Stapel gezogen
    boolean stapelkarteGezogen = false;
    // Die aktuell vom Stapel genommene Karte
    Farbkarte gezogeneKarte;

    /**
     * Getter-Methode um die vom Stapel genommene Karte zurück zugeben
     */
    public Farbkarte getGezogeneKarte() {
        return gezogeneKarte;
    }

    /**
     * Main-Methode der Klasse Spiel
     */
    public static void main(String[] args) {

        Thread thSpiel = new Thread(new Spiel());
        thSpiel.start();
        try {
            thSpiel.join();
        } catch (InterruptedException ex) {
            Logger.getLogger(Spiel.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * Methode um eine Instanz eines Spieles mit 4 Spielern zu erzeugen. Spiel
     * wird vorbereitet: 1) Spieler erzeugen 2) Reihen erzeugen 3) Karten-
     * Stapel vorbereiten 4) Startfarbkarte den Spielern zuteilen
     *
     * @param player1
     * @param player2
     * @param player3
     * @param player4
     */
    public void GameMain(String player1, String player2, String player3, String player4) {
        // Spiel vorbereiten
        // Anlegen der vier Spieler
        Spieler = new Spieler[MAXSPIELER + 1];
        for (int i = 1; i <= MAXSPIELER; i++) {
            Spieler[i] = new Spieler();
        }
        // Spieler erzeugen
        Spieler[1] = new Spieler(player1, false, 1);
        Spieler[2] = new Spieler(player2, false, 2);
        Spieler[3] = new Spieler(player3, false, 3);
        Spieler[4] = new Spieler(player4, false, 4);

        // Reihenkarten legen - Reihenobjekte erstellen      
        Reihe = new Reihe[4 + 1];
        for (int i = 1; i <= 4; i++) {
            Reihe[i] = new Reihe();
        }
        // Methodenaufruf, um alle Reihen zu aktivieren
        alleReihenAktivieren();
        // Kartenstapel vorbereiten
        GemischterStapel = new GemischterStapel();
        // Methodenaufruf, um jedem Spieler eine Start Farbkarte zu zuteilen
        startFarbkartenVerteilen();
        // alle Spieler aktivieren
        alleSpielerAktivieren();
        // Spieler 1 fängt das Spiel an
        aktuellerSpieler = 1;
    }

    /**
     * Methode um jedem Spieler zu beginn des Spiels eine Farbkarte zu
     * zuteilen. Jeder Spieler erhält einen andere Farbkarte
     */
    public void startFarbkartenVerteilen() {
        // Start Farbkarte Spieler 1
        Farbkarte k1 = GemischterStapel.karteAufnehmen();
        GemischterStapel.letzteKarteLöschen();
        Spieler[1].karteAufnehmen(k1);
        // Kartenwert in einen String umwandeln, um diese anschliessend  mit den
        // anderen Gezogenen Karten vergleichen zu könnene
        String s1 = k1.getFarbe();
        // Start Farbkarte Spieler 2
        Farbkarte k2 = GemischterStapel.karteAufnehmen();
        GemischterStapel.letzteKarteLöschen();
        String s2 = k2.getFarbe();
        // ist die aufgenommene Karte die gleich wie die Karte von Spieler 1
        // wird solange eine neue Karte gezogen, bis eine anders farbige Karte 
        // gezogen wurde
        while (s2.equals(s1)) {
            k2 = GemischterStapel.neueKarte(k2);
            GemischterStapel.letzteKarteLöschen();
            s2 = k2.getFarbe();
        }
        // Karte Spieler 2 übergeben
        Spieler[2].karteAufnehmen(k2);
        // Start Farbkarte für Spieler 3
        Farbkarte k3 = GemischterStapel.karteAufnehmen();
        String s3 = k3.getFarbe();
        GemischterStapel.letzteKarteLöschen();
        // ist die aufgenommene Karte die gleich wie die Karte von Spieler 1 und 2
        while (s3.equals(s1) || s3.equals(s2)) {
            k3 = GemischterStapel.neueKarte(k3);
            GemischterStapel.letzteKarteLöschen();
            s3 = k3.getFarbe();
        }
        // Karte Spieler 3 übergeben  
        Spieler[3].karteAufnehmen(k3);
        // Start Farbkarte für Spieler 4
        Farbkarte k4 = GemischterStapel.karteAufnehmen();
        String s4 = k4.getFarbe();
        GemischterStapel.letzteKarteLöschen();
        // ist die aufgenommene Karte die gleich wie die Karte von Spieler 1, 2 und 3
        while (s4.equals(s1) || s4.equals(s2) || s4.equals(s3)) {
            k4 = GemischterStapel.neueKarte(k4);
            GemischterStapel.letzteKarteLöschen();
            s4 = k4.getFarbe();
        }
        // Karte Spieler 4 übergeben  
        Spieler[4].karteAufnehmen(k4);
    }

    /**
     * Methode um eine Karte vom Stapel zu nehmen. <br> <br> Dies Methode wird
     * aufgerufen, wenn ein Spieler auf den Kartenstapel klickt. Dabei wird
     * geprüft, ob es: - die Letzte-Runde-Karte ist, wenn ja wird diese auf
     * demGUI angezeigt - noch Reihen gibt welche noch nicht voll sind. Falls
     * alle voll sind, kann keine Karte vom Stapel genommen werden!
     * 
     * @return gezogenen Farbkarte
     */
    public Farbkarte karteAufnehmen() {
        // Prüft, ob die noch aktiven Reihen voll sind. Es muss mindestens eine
        // Reihe vorhanden sein, die nicht voll ist, damit eine Karte aufgenommen
        // werden kann.
        if (anzahlAktiverReihen() == anzahlAktiverVollerReihen()) {
            throw new InternalError("Es sind alle Reihen voll!! Sie müssen eine Reihe aufnehmen!");
        }
        // 
        if (anzahlAktiverVollerReihen() == 4) {
            // aktion nichtmöglich
            // gezogenen Karte wird auf "null" gesetzt
            gezogeneKarte = null;
        } else {
            // Es kann eine Karte vom Stapel genommen werden!
            gezogeneKarte = GemischterStapel.karteAufnehmen();
            GemischterStapel.letzteKarteLöschen();
            // Ist die gezogene Karte letzte Rundekarte? Stapelgrösse 15?
            // Wenn ja, wird ein Boolean gesetzt welcher beim GUI angefragt wird,
            // und dann die letzte Runde-Karte auf dem GUI anzeigt
            if (GemischterStapel.stapelGroesse() == letzteRundeKarte) {
                letzteRundekarteGezogen = true;
            }
        }
        stapelkarteGezogen = true;
        return gezogeneKarte;
    }

    /**
     * Methode um die vom Stapel genommene Farbkarte einer Reihe zu zuweisen
     * oder falls keine Farbkarte gezogen wurde, die Reihe auf zunehmen. Diese
     * Methode wird aufgerufen, wenn ein Spieler auf eine Reihe klickt. Es wird
     * geprüft ob die Karte auf die angegeben Reihe gelegt werden kann. Wenn ja
     * wird Karte auf die Reihe gelegt und der nächste Spieler aufgerufen. Wenn
     * nein, wird false zurück gegeben und der Spieler muss eine andere Reihe
     * wählen
     *
     * @param gezogeneKarte welche zuvor vom Stapel genommen wurde
     * @param reihe welche angeklickt wurde
     * @return boolean ob dieser Zug möglich ist oder nicht
     */
    public boolean karteAufReiheLegen(Farbkarte gezogeneKarte, int reihe) {
        boolean aktionMoeglich;
        // Prüft ob:
        // - Die angewählte Reihe nicht voll ist
        // - eine Farbkarte vom Stapel genommen wurde
        // - die gewählte Reihe aktiv ist (noch nicht von einem Spieler auf-
        //   genommen wurde)
        // Wenn alle Bedingungen erfüllt sind, wird die Karte auf die Reihe 
        // gelegt
        if (Reihe[reihe].ReiheVoll() == false && stapelkarteGezogen == true && Reihe[reihe].reiheAktiv == true) {
            Reihe[reihe].karteHinzufügen(gezogeneKarte);
            stapelkarteGezogen = false;
            aktuellerSpieler = nextPlayer(aktuellerSpieler);
            gezogeneKarte = null;
            aktionMoeglich = true;
        } else {
            // Falls die Riehe voll ist und zuvor keine Farbkarte gezogen wurde,
            // Wird eine Fehlermeldung ausgegebn, dass der Spieler eine andere
            // Reihe wählen muss
            aktionMoeglich = false;
            if (Reihe[reihe].ReiheVoll() == true && stapelkarteGezogen == true) {
                throw new InternalError("Diese Reihe ist voll!! Sie müssen eine andere Reihe wählen!");
            }
            // Falls ein Spieler auf eine Reihe klick ohne zuvor einen Karte
            // vom Stapel genommen zu haben, wird die ausgewählte Reihe auf-
            // genommen -> Karten dem Spieler übergeben
            if (stapelkarteGezogen == false) {
                // Methoden aufruf, um die Reihe aufzunehmen
                reiheAufnehmen(reihe);
            }
        }
        return aktionMoeglich;
    }

    /**
     * Methode um eine Reihe aufzunehmen. Es wird überprüft ob 1) die Reihe
     * aktiv ist 2) und die Reihe mindestens ein Karte enthält. Wenn ok: Die
     * Reihe wird aufgenommen und die enthaltenen Karten dem Spieler übergeben
     * und der Spieler wird inaktiv gesetzt.
     * 
     * @param gewaehlteReihe welche aufgenommen werden möchte
     * @return boolean ob die Reihe aufgenommen werden darf
     */
    public boolean reiheAufnehmen(int gewaehlteReihe) {
        boolean aktionMoeglich;
        // Es muss mindestens eine Karte auf dem Tisch liegen
        if (anzahlKartenAufTisch() == 0) {
            aktionMoeglich = false;
            throw new InternalError("Es gibt keine Reihe zum Aufnehme! Bitte nehmen Sie eine Karte vom Stapel");
        } else {
            // Die Reihe muss mindestens eine Karte enthalten
            if (Reihe[gewaehlteReihe].reiheGroesse() == 0) {
                aktionMoeglich = false;
                throw new InternalError("Diese Reihe hat keine Karten zum aufnehmen! Wählen Sie eine andere Reihe!");
            } else {
                // Ist die Reihe nicht aktiv und es gibt keine Karten, ist diese
                // aktion nicht möglich
                if (Reihe[gewaehlteReihe].reiheAktiv == false && Reihe[gewaehlteReihe].reiheGroesse() == 0) {
                    aktionMoeglich = false;
                } else {
                    // Reihe kann aufgenommen werden
                    aktionMoeglich = true;
                    // Reihe dem Spieler übergeben
                    // Wie viele Karten sind auf der Reihe?
                    int GroesseStapel = Reihe[gewaehlteReihe].reiheGroesse();
                    // Alle Reihenkarten dem Spieler übergeben
                    for (int i = 0; i < GroesseStapel; i++) {
                        Farbkarte karte = Reihe[gewaehlteReihe].karteVonReihe(i);
                        Spieler[aktuellerSpieler].karteAufnehmen(karte);
                    }
                    // Reihe löschen
                    Reihe[gewaehlteReihe].alleKartenLöschen();
                    // Spieler inaktiv setzten
                    Spieler[aktuellerSpieler].setIstAktiv(false);
                    // Sind noch aktive Spieler vorhanden? Hat es noch aktive Reihenkarten? 
                    // Wenn es noch aktive Reihen hat, hat es auch noch aktive Spieler
                    anzahlAktiverReihen = anzahlAktiverReihen();
                    if (anzahlAktiverReihen > 0) {
                        // Es hat noch aktive Reihen / Spieler:
                        // Nächster Spieler ist an der Reihe
                        aktuellerSpieler = nextPlayer(aktuellerSpieler);
                    } else {
                        // Es hat keine aktiven Reihen / Spieler mehr:
                        // Runde ist zu ende!
                        // Wurde die letzte Rundekarte bereits gezogen?
                        if (letzteRundekarteGezogen == true) {
                            // JA, letzte Rundekarte wurde gezogen
                            // ****************** SPIEL ENDE *******************
                            spielEnde = true;
                        } else {
                            // NEIN, letzte Rundekarte wurde noch nicht gezogen
                            // Runde ist zu ende --> nächste Runde
                            // nächster Spieler
                            aktuellerSpieler = nextPlayer(aktuellerSpieler);
                            // alle Spieler wieder aktivieren
                            alleSpielerAktivieren();
                            // alle Reihen wieder aktivieren
                            alleReihenAktivieren();
                            anzahlAktiverReihen = 4;
                            anzahlAktiverVollerReihen = 0;
                            rundeEnde = true;
                        }
                    }
                }
            }
        }
        return aktionMoeglich;
    }

    /**
     * Methode gibt die anzahl Aktiver Reihen zurück.
     *
     * @return anzahlAktiverReihen
     */
    public int anzahlAktiverReihen() {
        anzahlAktiverReihen = 0;
        for (int i = 1; i <= 4; i++) {
            if (Reihe[i].reiheAktiv == true) {
                anzahlAktiverReihen++;
            }
        }
        return anzahlAktiverReihen;
    }

    /**
     * Methode gibt die Anzahl aktiver und Reihen mit 3 Farbkarten zurück.
     *
     * @return anzahlAktiverVollerReihen
     */
    public int anzahlAktiverVollerReihen() {
        anzahlAktiverVollerReihen = 0;
        for (int i = 1; i <= 4; i++) {
            if (Reihe[i].ReiheVoll() == true) {
                anzahlAktiverVollerReihen++;
            }
        }
        return anzahlAktiverVollerReihen;
    }

    /**
     * Methode um alle Reihen zu aktivieren.
     */
    public void alleReihenAktivieren() {
        for (int i = 1; i <= 4; i++) {
            Reihe[i].ReiheAktivieren();
        }
    }

    /**
     * Methode um alle Spieler zu aktivieren
     */
    public void alleSpielerAktivieren() {
        for (int i = 1; i <= MAXSPIELER; i++) {
            Spieler[i].setIstAktiv(true);
        }
    }

    /**
     * Gibt die Anzahl der Karten auf dem Tisch zurück
     */
    public int anzahlKartenAufTisch() {
        anzahlKartenAufTisch = 0;
        for (int i = 1; i <= 4; i++) {
            int Groesse = Reihe[i].reiheGroesse();
            anzahlKartenAufTisch = anzahlKartenAufTisch + Groesse;
        }
        return anzahlKartenAufTisch;
    }

    /**
     * Getter für nächster Spieler.
     *
     * @param aktuellerSpieler
     * @return nächster Spieler
     */
    public int nextPlayer(int aktuellerSpieler) {
        int nextPlayer = 0;
        switch (aktuellerSpieler) {
            case 1:
                if (Spieler[2].IstAktiv()) {
                    nextPlayer = 2;
                    break;
                } else if (Spieler[3].IstAktiv()) {
                    nextPlayer = 3;
                    break;
                } else if (Spieler[4].IstAktiv()) {
                    nextPlayer = 4;
                    break;
                } else {
                    nextPlayer = 1;
                    break;
                }
            case 2:
                if (Spieler[3].IstAktiv()) {
                    nextPlayer = 3;
                    break;
                } else if (Spieler[4].IstAktiv()) {
                    nextPlayer = 4;
                    break;
                } else if (Spieler[1].IstAktiv()) {
                    nextPlayer = 1;
                    break;
                } else {
                    nextPlayer = 2;
                    break;
                }
            case 3:
                if (Spieler[4].IstAktiv()) {
                    nextPlayer = 4;
                    break;
                } else if (Spieler[1].IstAktiv()) {
                    nextPlayer = 1;
                    break;
                } else if (Spieler[2].IstAktiv()) {
                    nextPlayer = 2;
                    break;
                } else {
                    nextPlayer = 3;
                    break;
                }
            case 4:
                if (Spieler[1].IstAktiv()) {
                    nextPlayer = 1;
                    break;
                } else if (Spieler[2].IstAktiv()) {
                    nextPlayer = 2;
                    break;
                } else if (Spieler[3].IstAktiv()) {
                    nextPlayer = 3;
                    break;
                } else {
                    nextPlayer = 4;
                    break;
                }
        }
        return nextPlayer;
    }

    /**
     * Methode Prüft ob eine Stapelkarte gezogen wurde
     *
     * @return Stapelkarte gezogen ja oder nein
     */
    public boolean stapelkarteGezogen() {
        return stapelkarteGezogen;
    }

    /**
     * Methode gibt den Farbwert der gezogenen Farbkarte zurück
     *
     * @return Farbkartenewert
     */
    public Farbkarte gezogeneKarte() {
        return gezogeneKarte;
    }

    /**
     * Gibt einen boolean zurück ob das Spiel zu ende ist.
     *
     * @return Spiel zu ende ja oder nein
     */
    public boolean spielEnde() {
        return spielEnde;
    }

    /**
     * Methode gibt den aktuellen Spielstand zurück.
     *
     * @return Spielstand
     */
    public Spielstand GetSpielStand() {
        return new Spielstand(this, Spieler, Reihe);
    }

    /**
     * Methode enthält alle Attribute welche für die Darstellung der Randliste
     * am Ende eines Spieles benötigt werden. Die Resulatate werden auf die
     * Datenbank geschrieben.
     *
     * @return Randliste
     */
    public GameFinishedMessage GetHighscore() {
        if (!spielEnde()) {
            return null;
        }
        GameFinishedMessage mes = new GameFinishedMessage();
        mes.Spieler1 = Spieler[1].getName();
        mes.Spieler2 = Spieler[2].getName();
        mes.Spieler3 = Spieler[3].getName();
        mes.Spieler4 = Spieler[4].getName();

        mes.PunkteSpieler1 = Spieler[1].punkteZählen();
        mes.PunkteSpieler2 = Spieler[2].punkteZählen();
        mes.PunkteSpieler3 = Spieler[3].punkteZählen();
        mes.PunkteSpieler4 = Spieler[4].punkteZählen();
        
        //Aufruf der Klasse Writehighscore
        Writehighscore writehighscore = new Writehighscore(mes);

        return mes;
    }

    /**
     *
     */
    @Override
    public void run() {
        GameMain("p1", "p2", "p3", "p4");
    }
}